/* @flow */

import config from '../config'
import {warn} from './debug'
import {inBrowser, inWeex} from './env'
import {isPromise} from 'shared/util'
import {pushTarget, popTarget} from '../observer/dep'

/**
 * 2019-05-15
 * 在 Vue 的源码中 handleError 函数的使用一般如下
 *    try {
 *      handlers[i].call(vm)
 *    } catch (e) {
 *      handleError(e, vm, `${hook} hook`)
 *    }
 * 上面是生命周期钩子回调执行时的代码，
 *    由于生命周期钩子是开发者自定义的函数， * 这个函数的执行是很可能存在运行时错误的， * 所以这里需要 try catch 包裹，
 *    且在发生错误的时候，在 catch 语句块中捕获错误，然后使用 handleError 进行错误处理
 */
export function handleError(err: Error, vm: any, info: string) {
  // Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
  // See: https://github.com/vuejs/vuex/issues/1505
  pushTarget()
  try {
    if (vm) {
      /**
       * 2019-05-15
       * errorCaptured定义在父组件中，处理子组件在生命周期函数异常
       * 一般用法如下：
       *    var ChildComponent = {
       *      template: '<div>child component</div>',
       *      beforeCreate: function () {
       *        JSON.parse("};")
       *      }
       *    }
       *    var vm = new Vue({
       *      components: {
       *        ChildComponent
       *      },
       *      errorCaptured: function (err, vm, info) {
       *        console.log(err)
       *        console.log(vm)
       *        console.log(info)
       *      }
       *   });
       */
      let cur = vm
      while ((cur = cur.$parent)) {
        const hooks = cur.$options.errorCaptured
        if (hooks) {
          for (let i = 0; i < hooks.length; i++) {
            try {
              const capture = hooks[i].call(cur, err, vm, info) === false
              if (capture) return
            } catch (e) {
              globalHandleError(e, cur, 'errorCaptured hook')
            }
          }
        }
      }
    }
    globalHandleError(err, vm, info)
  } finally {
    popTarget()
  }
}

export function invokeWithErrorHandling(handler: Function,
                                        context: any,
                                        args: null | any[],
                                        vm: any,
                                        info: string) {
  let res
  try {
    res = args ? handler.apply(context, args) : handler.call(context)
    if (res && !res._isVue && isPromise(res) && !res._handled) {
      res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
      // issue #9511
      // avoid catch triggering multiple times when nested calls
      res._handled = true
    }
  } catch (e) {
    handleError(e, vm, info)
  }
  return res
}
/**
 * 2019-05-15
 * config.errorHandler 就是 Vue 全局API提供的用于自定义错误处理的配置
 */
function globalHandleError(err, vm, info) {
  if (config.errorHandler) {
    try {
      return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      // if the user intentionally throws the original error in the handler,
      // do not log it twice
      if (e !== err) {
        logError(e, null, 'config.errorHandler')
      }
    }
  }
  logError(err, vm, info)
}

function logError(err, vm, info) {
  if (process.env.NODE_ENV !== 'production') {
    warn(`Error in ${info}: "${err.toString()}"`, vm)
  }
  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console !== 'undefined') {
    console.error(err)
  } else {
    throw err
  }
}
